﻿using System;
using System.Linq;
using System.Runtime.InteropServices;

namespace RayDen.Library.Core.Primitives {
    [StructLayout(LayoutKind.Explicit)]
    [Serializable]
    public struct TriangleInfo : ISerializableValue {
        public static int delta = 0;
        [FieldOffset(0)] public uint v0;
        [FieldOffset(4)] public uint v1;
        [FieldOffset(8)] public uint v2;

        public TriangleInfo(int p, int p_2, int p_3)
        {
            this.v0 = (uint)(p   - delta);
            this.v1 = (uint)(p_2 - delta);
            this.v2 = (uint)(p_3 - delta);
        }

        public AABB WorldBound(Point[] vertices)
        {
            var pts = vertices;
            var res = new AABB(pts[this.v0], pts[this.v1]);
            res.Union(pts[v2]);
            return res;
        }

        public Point Center(Point[] vertices)
        {
            var v = WorldBound(vertices).Center;
            return v;
        }

        public Vector Sum(Point[] vertices)
        {
            var v = WorldBound( vertices).Sum;
            return v;
        }

        public Normal ComputeNormal(Vector[] vertices)
        {
            return (Normal)((vertices[v1] - vertices[v0]) ^ (vertices[v2] - vertices[v0])).Normalize();
            //return (Normal)((vertices[v0] - vertices[v1]) ^ (vertices[v0] - vertices[v2]));
        }

        public Normal ComputeVerticeNormal(int vInd, Vector[] meshVertices)
        {
            Vector v_0, v_1, v_2;

            switch (vInd)
            {
                case 0:
                    v_0 = meshVertices[v0];
                    v_1 = meshVertices[v1];
                    v_2 = meshVertices[v2];
                    break;
                case 1:
                    v_0 = meshVertices[v1];
                    v_1 = meshVertices[v0];
                    v_2 = meshVertices[v2];
                    break;
                case 2:
                    v_0 = meshVertices[v2];
                    v_1 = meshVertices[v1];
                    v_2 = meshVertices[v0];
                    break;
                default:
                    throw new ArgumentException();
            }
            //var Normal = (Normal)((v_1 - v_0) ^ (v_2 - v_0)).Normalize();
            var Normal = (Normal) ((v_0 - v_1) ^ (v_0 - v_2)).Normalize();

            return Normal;
        }

        public float AreaV(Vector[] meshVertices)
        {
            var verts = meshVertices;
            var p0 = verts[v0];
            var p1 = verts[v1];
            var p2 = verts[v2];

            return 0.5f * ((p1 - p0) ^ (p2 - p0)).Length;
        }


        public void Sample(Vector[] meshVertices, float u0, float u1, out Vector samplePoint, out float b0, out float b1, out float b2)
        {
            b0 = 0f;
            b1 = 0f;
            MC.UniformSampleTriangle(u0, u1, ref  b0, ref b1);

            var p0 = meshVertices[v0];
            var p1 = meshVertices[v1];
            var p2 = meshVertices[v2];
            b2 = 1f - (b0) - (b1);
            samplePoint = (b0) * p0 + (b1) * p1 + (b2) * p2;
        }

        public Normal InterpolateNormal(Vector[] meshV, float b1, float b2)
        {
            var n0 = this.ComputeVerticeNormal(0,  meshV);
            var n1 = this.ComputeVerticeNormal(1,  meshV);
            var n2 = this.ComputeVerticeNormal(2,  meshV);
            float b0 = 1f - b1 - b2;
            return (b0 * n0 + b1 * n1 + b2 * n2).Normalize();
        }

        public byte[] Serialize()
        {
            return (new[]
                {
                    BitConverter.GetBytes(v0),
                    BitConverter.GetBytes(v1),
                    BitConverter.GetBytes(v2),
                }).SelectMany(x => x).ToArray();
        }

        public void Deserialize(byte[] data, int offset = 0)
        {
            v0 = BitConverter.ToUInt32(data, offset);
            v1 = BitConverter.ToUInt32(data, offset + 4);
            v2 = BitConverter.ToUInt32(data, offset + 8);
        }
    }
}